/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.bpmn.behavior;

import com.alibaba.fastjson2.JSONObject;
import com.google.common.base.Strings;
import com.je.bpm.core.model.*;
import com.je.bpm.core.model.config.CounterSignPassTypeEnum;
import com.je.bpm.core.model.config.task.TaskApprovalNoticeConfigImpl;
import com.je.bpm.core.model.config.task.assignment.TaskAssigneeConfigImpl;
import com.je.bpm.core.model.event.BoundaryEvent;
import com.je.bpm.core.model.process.Process;
import com.je.bpm.core.model.task.KaiteBaseUserTask;
import com.je.bpm.core.model.task.KaiteCounterSignUserTask;
import com.je.bpm.engine.ActivitiIllegalArgumentException;
import com.je.bpm.engine.approvalnotice.ActivitiApprovalNotice;
import com.je.bpm.engine.approvalnotice.TaskApprovalNoticeEnum;
import com.je.bpm.engine.delegate.BpmnError;
import com.je.bpm.engine.delegate.DelegateExecution;
import com.je.bpm.engine.delegate.ExecutionListener;
import com.je.bpm.engine.delegate.Expression;
import com.je.bpm.engine.delegate.event.ActivitiCountersignedOpinionType;
import com.je.bpm.engine.delegate.event.ActivitiEventType;
import com.je.bpm.engine.delegate.event.impl.ActivitiEventBuilder;
import com.je.bpm.engine.history.HistoricActivityInstance;
import com.je.bpm.engine.history.HistoricTaskInstance;
import com.je.bpm.engine.history.HistoricVariableInstance;
import com.je.bpm.engine.impl.HistoricTaskInstanceQueryImpl;
import com.je.bpm.engine.impl.HistoricVariableInstanceQueryImpl;
import com.je.bpm.engine.impl.Page;
import com.je.bpm.engine.impl.bpmn.helper.ErrorPropagation;
import com.je.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import com.je.bpm.engine.impl.cmd.CompleteTaskCmd;
import com.je.bpm.engine.impl.cmd.SubmitTypeEnum;
import com.je.bpm.engine.impl.context.Context;
import com.je.bpm.engine.impl.delegate.ActivityBehavior;
import com.je.bpm.engine.impl.delegate.SubProcessActivityBehavior;
import com.je.bpm.engine.impl.identity.Authentication;
import com.je.bpm.engine.impl.interceptor.CommandContext;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntity;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntityImpl;
import com.je.bpm.engine.impl.util.CollectionUtil;
import com.je.bpm.engine.impl.util.ProcessDefinitionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 多实例活动行为
 * Implementation of the multi-instance functionality as described in the BPMN 2.0 spec.
 * <p>
 * Multi instance functionality is implemented as an {@link ActivityBehavior} that wraps the original {@link ActivityBehavior} of the activity.
 * <p>
 * Only subclasses of {@link AbstractBpmnActivityBehavior} can have multi-instance behavior. As such, special logic is contained in the {@link AbstractBpmnActivityBehavior} to delegate to the
 * {@link MultiInstanceActivityBehavior} if needed.
 */
public abstract class MultiInstanceActivityBehavior extends FlowNodeActivityBehavior implements SubProcessActivityBehavior {

    private static final long serialVersionUID = 1L;

    protected static final Logger LOGGER = LoggerFactory.getLogger(MultiInstanceActivityBehavior.class);

    // Variable names for outer instance(as described in spec)
    /**
     * 多实例总数
     */
    public static final String NUMBER_OF_INSTANCES = "nrOfInstances";
    /**
     * 当前活动的实例数，即尚未完成的实例数。对于串行多实 例，这个值总是1。
     */
    public static final String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";
    /**
     * 会签通过比例
     */
    public static final String NUMBER_OF_ACTIVE_AMOUNT = "amount";
    /**
     * 已完成实例的数量
     */
    public static final String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";
    /**
     * 通过数量
     */
    public static final String NUMBER_OF_PASS_INSTANCES = "nrOfPassInstances";
    /**
     * 否决数量
     */
    public static final String NUMBER_OF_VETO_INSTANCES = "nrOfVetoInstances";
    /**
     * 弃权数量
     */
    public static final String NUMBER_OF_ABSTAIN_INSTANCES = "nrOfAbstainInstances";
    /**
     * 通过负责人
     */
    public static final String PASS_PRINCIPAL = "oneBallotUserId";
    /**
     * 负责人意见
     */
    public static final String PRINCIPAL_OPINION = "principalOpinion";
    /**
     * 审批人意见
     */
    public static final String CUSTOMER_MESSAGE = "customerMessage";
    /**
     * 会签类型
     */
    public static final String COUNTERSIGN_PASS_TYPE = "counterSignPassType";
    /**
     * 会签提交人
     */
    public static final String COUNTERSIGN_PASS_USER = "countersignPassUser";
    /**
     * voteAll 全部处理
     */
    public static final String COUNTERSIGN_VOTE_ALL = "countersignVoteAll";
    /**
     * 人员处理信息
     */
    public static final String PROCESSING_INFO = "processingInfo";
    /**
     * 人员信息
     */
    public static final String PROCESSING_USERS_INFO = "datas";
    /**
     * 顺序审批人员信息
     */
    public static final String EACH_LOOP_USER = "eachLoopUser";
    protected int nrOfPassInstances;
    protected int nrOfVetoInstances;
    protected int nrOfAbstainInstances;
    protected String principalOpinion;
    protected String oneBallotUserId;
    // Instance members
    protected Activity activity;
    protected AbstractBpmnActivityBehavior innerActivityBehavior;
    protected Expression loopCardinalityExpression;
    protected Expression completionConditionExpression;
    protected Expression collectionExpression;
    protected String collectionVariable;
    protected String collectionElementVariable;
    // default variable name for loop counter for inner instances (as described in the spec)
    protected String collectionElementIndexVariable = "loopCounter";
    private String loopDataOutputRef;
    private String outputDataItem;

    /**
     * @param activity              The {@link Activity} which has multi instance behaviour
     * @param innerActivityBehavior The original {@link ActivityBehavior} of the activity that will be wrapped inside this behavior.
     */
    public MultiInstanceActivityBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
        this.activity = activity;
        setInnerActivityBehavior(innerActivityBehavior);
    }

    @Override //第一次进入，这里运行多次  2
    public void execute(DelegateExecution execution) {
        if (getLocalLoopVariable(execution, getCollectionElementIndexVariable()) == null) {
            clearLoopDataOutputRef(execution);
            int nrOfInstances = 0;
            try {
                nrOfInstances = createInstances(execution);
            } catch (BpmnError error) {
                ErrorPropagation.propagateError(error, execution);
            }
            if (nrOfInstances == 0) {
                super.leave(execution);
            }
        } else {
            getCommandContext().getHistoryManager().recordActivityStart((ExecutionEntity) execution);
            innerActivityBehavior.execute(execution);
        }
    }

    private void clearLoopDataOutputRef(DelegateExecution execution) {
        if (hasLoopDataOutputRef()) {
            execution.setVariable(getLoopDataOutputRef(), new ArrayList<>());
        }
    }

    protected abstract int createInstances(DelegateExecution execution);

    public String getAssigneeUser(DelegateExecution execution) {
        String assigneeUser = String.valueOf(execution.getVariable(((ExecutionEntityImpl) execution).getActivityId()));
        if (!Strings.isNullOrEmpty(assigneeUser) && !assigneeUser.equals("null")) {
            return assigneeUser;
        }
        ProcessEngineConfigurationImpl processEngineConfiguration = Context.getProcessEngineConfiguration();
        BpmnModel bpmnModel = processEngineConfiguration.getRepositoryService().
                getBpmnModel(execution.getProcessDefinitionId());
        FlowElement targetFlowElement = bpmnModel.getFlowElement(((ExecutionEntityImpl) execution).getActivityId());
        if (targetFlowElement instanceof KaiteBaseUserTask) {
            TaskAssigneeConfigImpl taskAssigneeConfig = ((KaiteBaseUserTask) targetFlowElement).getTaskAssigneeConfig();
            assigneeUser = getUserIds(bpmnModel, ((KaiteBaseUserTask) targetFlowElement), getCommandContext(), taskAssigneeConfig, execution, "");
        }
        validatePersonnelClearance(Arrays.asList(assigneeUser.split(",")), targetFlowElement);
        return assigneeUser;
    }

    public Boolean isPass(DelegateExecution execution) {
        //负责人决定制
        if (getStringVariable(execution, COUNTERSIGN_PASS_TYPE).equals(CounterSignPassTypeEnum.PASS_PRINCIPAL.toString())) {
            String principalOpinion = getStringVariable(execution, PRINCIPAL_OPINION);
            if (principalOpinion.equals(ActivitiCountersignedOpinionType.PASS.toString())) {
                return true;
            } else {
                return false;
            }
        } else if (getStringVariable(execution, COUNTERSIGN_PASS_TYPE).equals(CounterSignPassTypeEnum.PASS_PERSENT.toString())) {
            int amount = getLoopVariable(execution, NUMBER_OF_ACTIVE_AMOUNT);
            int nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES);
            int nrOfAbstainInstances = getLoopVariable(execution, NUMBER_OF_ABSTAIN_INSTANCES);
            //总数-弃权数量
            nrOfInstances -= nrOfAbstainInstances;
            int nrOfPassInstances = getLoopVariable(execution, NUMBER_OF_PASS_INSTANCES);
            if (nrOfPassInstances == 0) {
                return false;
            }
            if (nrOfPassInstances * 100 / nrOfInstances >= amount) {
                return true;
            } else {
                return false;
            }
        }
        return true;
    }

    /**
     * 添加人员处理信息到变量里面
     *
     * @param execution
     */
    public void addProcessingInfo(DelegateExecution execution, String user, String opinion) {
        if (Strings.isNullOrEmpty(user)) {
            return;
        }
        JSONObject processInfo = getJsonObject(execution, PROCESSING_INFO);
        if (processInfo == null) {
            JSONObject jsonObject = new JSONObject();
            JSONObject opinions = new JSONObject();
            opinions.put(user, opinion);
            jsonObject.put("opinions", opinions);
            setLoopVariable(execution, PROCESSING_INFO, jsonObject);
        } else {
            HashMap<String, String> opinions = (HashMap<String, String>) processInfo.get("opinions");
            opinions.put(user, opinion);
            setLoopVariable(execution, PROCESSING_INFO, processInfo);
        }
    }

    /**
     * 添加会签变量
     */
    public void addCounterVariable(ExecutionEntityImpl execution) {
        String activityId = execution.getActivityId();
        ProcessEngineConfigurationImpl processEngineConfiguration = Context.getProcessEngineConfiguration();
        BpmnModel bpmnModel = processEngineConfiguration.getRepositoryService().getBpmnModel(execution.getProcessDefinitionId());
        FlowElement flowElement = bpmnModel.getMainProcess().getFlowElement(activityId);
        if (flowElement instanceof KaiteCounterSignUserTask) {
            KaiteCounterSignUserTask kaiteCounterSignUserTask = (KaiteCounterSignUserTask) flowElement;
            setLoopVariable(execution, NUMBER_OF_ACTIVE_AMOUNT, kaiteCounterSignUserTask.getCounterSignConfig().getAmount());
            setLoopVariable(execution, NUMBER_OF_PASS_INSTANCES, 0);
            setLoopVariable(execution, NUMBER_OF_VETO_INSTANCES, 0);
            setLoopVariable(execution, NUMBER_OF_ABSTAIN_INSTANCES, 0);
            setLoopVariable(execution, PASS_PRINCIPAL, kaiteCounterSignUserTask.getCounterSignConfig().getOneBallotUserId());
            setLoopVariable(execution, PRINCIPAL_OPINION, "");
            setLoopVariable(execution, COUNTERSIGN_PASS_TYPE, kaiteCounterSignUserTask.getCounterSignConfig().getCounterSignPassType().toString());
            setLoopVariable(execution, COUNTERSIGN_VOTE_ALL, kaiteCounterSignUserTask.getCounterSignConfig().isVoteAll());
        }
    }

    protected void executeCompensationBoundaryEvents(FlowElement flowElement, DelegateExecution execution) {
        //Execute compensation boundary events
        Collection<BoundaryEvent> boundaryEvents = findBoundaryEventsForFlowNode(execution.getProcessDefinitionId(), flowElement);
        if (CollectionUtil.isNotEmpty(boundaryEvents)) {
            // The parent execution becomes a scope, and a child execution is created for each of the boundary events
            for (BoundaryEvent boundaryEvent : boundaryEvents) {

                if (CollectionUtil.isEmpty(boundaryEvent.getEventDefinitions())) {
                    continue;
                }

                if (boundaryEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) {
                    ExecutionEntity childExecutionEntity = getCommandContext().getExecutionEntityManager()
                            .createChildExecution((ExecutionEntity) execution);
                    childExecutionEntity.setParentId(execution.getId());
                    childExecutionEntity.setCurrentFlowElement(boundaryEvent);
                    childExecutionEntity.setScope(false);

                    ActivityBehavior boundaryEventBehavior = ((ActivityBehavior) boundaryEvent.getBehavior());
                    boundaryEventBehavior.execute(childExecutionEntity);
                }
            }
        }
    }

    protected Collection<BoundaryEvent> findBoundaryEventsForFlowNode(final String processDefinitionId, final FlowElement flowElement) {
        Process process = getProcessDefinition(processDefinitionId);
        // This could be cached or could be done at parsing time
        List<BoundaryEvent> results = new ArrayList<BoundaryEvent>(1);
        Collection<BoundaryEvent> boundaryEvents = process.findFlowElementsOfType(BoundaryEvent.class, true);
        for (BoundaryEvent boundaryEvent : boundaryEvents) {
            if (boundaryEvent.getAttachedToRefId() != null && boundaryEvent.getAttachedToRefId().equals(flowElement.getId())) {
                results.add(boundaryEvent);
            }
        }
        return results;
    }

    protected Process getProcessDefinition(String processDefinitionId) {
        return ProcessDefinitionUtil.getProcess(processDefinitionId);
    }

    // Intercepts signals, and delegates it to the wrapped {@link ActivityBehavior}.
    @Override
    public void trigger(DelegateExecution execution, String signalName, Object signalData) {
        innerActivityBehavior.trigger(execution, signalName, signalData);
    }

    // required for supporting embedded subprocesses
    public void lastExecutionEnded(DelegateExecution execution) {
        //ScopeUtil.createEventScopeExecution((ExecutionEntity) execution);
        leave(execution);
    }

    // required for supporting external subprocesses
    @Override
    public void completing(DelegateExecution execution, DelegateExecution subProcessInstance) throws Exception {
    }

    // required for supporting external subprocesses
    @Override
    public void completed(DelegateExecution execution) throws Exception {
        leave(execution);
    }

    // Helpers
    // //////////////////////////////////////////////////////////////////////

    @SuppressWarnings("rawtypes")
    protected int resolveNrOfInstances(DelegateExecution execution) {
        if (loopCardinalityExpression != null) {
            return resolveLoopCardinality(execution);
        } else if (usesCollection()) {
            Collection collection = resolveAndValidateCollection(execution);
            return collection.size();
        } else {
            throw new ActivitiIllegalArgumentException("Couldn't resolve collection expression nor variable reference");
        }
    }

    @SuppressWarnings("rawtypes")
    protected void executeOriginalBehavior(DelegateExecution execution, int loopCounter) {
        if (usesCollection() && collectionElementVariable != null) {
            Collection collection = (Collection) resolveCollection(execution);

            Object value = null;
            int index = 0;
            Iterator it = collection.iterator();
            while (index <= loopCounter) {
                value = it.next();
                index++;
            }
            setLoopVariable(execution, collectionElementVariable, value);
        }

        execution.setCurrentFlowElement(activity);
        Context.getAgenda().planContinueMultiInstanceOperation((ExecutionEntity) execution);
    }

    @SuppressWarnings("rawtypes")
    protected Collection resolveAndValidateCollection(DelegateExecution execution) {
        Object obj = resolveCollection(execution);
        if (obj == null && execution.getParent() != null) {
            obj = resolveCollection(execution.getParent());
        }
        if (collectionExpression != null) {
            if (!(obj instanceof Collection)) {
                throw new ActivitiIllegalArgumentException(collectionExpression.getExpressionText() + "' didn't resolve to a Collection");
            }

        } else if (collectionVariable != null) {
            if (obj == null) {
                throw new ActivitiIllegalArgumentException("Variable " + collectionVariable + " is not found");
            }

            if (!(obj instanceof Collection)) {
                throw new ActivitiIllegalArgumentException("Variable " + collectionVariable + "' is not a Collection");
            }

        } else {
            throw new ActivitiIllegalArgumentException("Couldn't resolve collection expression nor variable reference");
        }
        return (Collection) obj;
    }

    protected Object resolveCollection(DelegateExecution execution) {
        Object collection = null;
        if (collectionExpression != null) {
            collection = collectionExpression.getValue(execution);

        } else if (collectionVariable != null) {
            collection = execution.getVariable(collectionVariable);
        }
        return collection;
    }

    protected boolean usesCollection() {
        return collectionExpression != null || collectionVariable != null;
    }

    protected boolean isExtraScopeNeeded(FlowNode flowNode) {
        return flowNode.getSubProcess() != null;
    }

    protected int resolveLoopCardinality(DelegateExecution execution) {
        // Using Number since expr can evaluate to eg. Long (which is also the default for Juel)
        Object value = loopCardinalityExpression.getValue(execution);
        if (value instanceof Number) {
            return ((Number) value).intValue();
        } else if (value instanceof String) {
            return Integer.valueOf((String) value);
        } else {
            throw new ActivitiIllegalArgumentException("Could not resolve loopCardinality expression '" + loopCardinalityExpression.getExpressionText() + "': not a number nor number String");
        }
    }

    public boolean completionConditionSatisfied(DelegateExecution execution) {
        //会签类型 比例通过制、负责人决定
        String type = getStringVariable(execution, COUNTERSIGN_PASS_TYPE);
        //全部处理
        Boolean voteAll = getBooleanVariable(execution, COUNTERSIGN_VOTE_ALL);
        if (Strings.isNullOrEmpty(type) || voteAll) {
            return false;
        }

        //比例通过
        if (type.equals(CounterSignPassTypeEnum.PASS_PERSENT.toString())) {
            //通过比例
            int passAmount = getLoopVariable(execution, NUMBER_OF_ACTIVE_AMOUNT);
            //未通过比例
            int vetoAmount = 100 - passAmount;
            if (vetoAmount == 0) {
                return false;
            }
            //多实例总数 -- 总处理数
            int nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES);
            //通过数量
            int nrOfPassInstances = getLoopVariable(execution, NUMBER_OF_PASS_INSTANCES);
            //否决数量
            int nrOfVetoInstances = getLoopVariable(execution, NUMBER_OF_VETO_INSTANCES);
            //弃权数量
            int nrOfAbstainInstances = getLoopVariable(execution, NUMBER_OF_ABSTAIN_INSTANCES);
            //通过数量*100/实际处理数量 >= 通过比例
            if (nrOfPassInstances * 100 / (nrOfInstances - nrOfAbstainInstances) >= passAmount) {
                return true;
            }
            //否决数量*100/实际处理数量 >= 未通过比例
            if (nrOfVetoInstances != 0) {
                if (nrOfVetoInstances * 100 / (nrOfInstances - nrOfAbstainInstances) >= vetoAmount) {
                    return true;
                }
            }
            return false;
        }
        //负责人
        if (type.equals(CounterSignPassTypeEnum.PASS_PRINCIPAL.toString())) {
            String principalOpinion = getStringVariable(execution, PRINCIPAL_OPINION);
            if (!Strings.isNullOrEmpty(principalOpinion)) {
                return true;
            }
        }
        return false;
    }

    protected void setLoopVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setVariableLocal(variableName, value);
    }

    protected String getStringVariable(DelegateExecution execution, String variableName) {
        if (execution == null) {
            return null;
        }
        Object value = execution.getVariable(variableName);
        return (String) (value != null ? value : "");
    }

    protected JSONObject getJsonObject(DelegateExecution execution, String variableName) {
        Object value = execution.getVariable(variableName);
        return (JSONObject) (value != null ? value : null);
    }

    protected Boolean getBooleanVariable(DelegateExecution execution, String variableName) {
        Object value = execution.getVariable(variableName);
        return (Boolean) (value != null ? value : false);
    }

    protected Integer getLoopVariable(DelegateExecution execution, String variableName) {
        Object value = execution.getVariableLocal(variableName);
        DelegateExecution parent = execution.getParent();
        while (value == null && parent != null) {
            value = parent.getVariableLocal(variableName);
            parent = parent.getParent();
        }
        return (Integer) (value != null ? value : 0);
    }

    protected Integer getLocalLoopVariable(DelegateExecution execution, String variableName) {
        return (Integer) execution.getVariableLocal(variableName);
    }

    protected void removeLocalLoopVariable(DelegateExecution execution, String variableName) {
        execution.removeVariableLocal(variableName);
    }

    /**
     * Since no transitions are followed when leaving the inner activity, it is needed to call the end listeners yourself.
     */
    protected void callActivityEndListeners(DelegateExecution execution) {
        getCommandContext().getProcessEngineConfiguration().getListenerNotificationHelper().executeExecutionListeners(activity, execution, ExecutionListener.EVENTNAME_END);
    }

    protected void logLoopDetails(DelegateExecution execution, String custom, int loopCounter, int nrOfCompletedInstances, int nrOfActiveInstances, int nrOfInstances) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Multi-instance '{}' {}. Details: loopCounter={}, nrOrCompletedInstances={},nrOfActiveInstances={},nrOfInstances={}",
                    execution.getCurrentFlowElement() != null ? execution.getCurrentFlowElement().getId() : "", custom, loopCounter,
                    nrOfCompletedInstances, nrOfActiveInstances, nrOfInstances);
        }
    }

    protected DelegateExecution getMultiInstanceRootExecution(DelegateExecution executionEntity) {
        DelegateExecution multiInstanceRootExecution = null;
        DelegateExecution currentExecution = executionEntity;
        while (currentExecution != null && multiInstanceRootExecution == null && currentExecution.getParent() != null) {
            if (currentExecution.isMultiInstanceRoot()) {
                multiInstanceRootExecution = currentExecution;
            } else {
                currentExecution = currentExecution.getParent();
            }
        }
        return multiInstanceRootExecution;
    }

    protected void dispatchActivityCompletedEvent(DelegateExecution execution) {
        ExecutionEntity executionEntity = (ExecutionEntity) execution;
        getCommandContext().getEventDispatcher().dispatchEvent(ActivitiEventBuilder.createActivityEvent(
                ActivitiEventType.ACTIVITY_COMPLETED,
                executionEntity.getActivityId(),
                executionEntity.getName(),
                executionEntity.getId(),
                executionEntity.getProcessInstanceId(),
                executionEntity.getProcessDefinitionId(),
                executionEntity.getCurrentFlowElement()
        ));
    }

    // Getters and Setters
    // ///////////////////////////////////////////////////////////

    public Expression getLoopCardinalityExpression() {
        return loopCardinalityExpression;
    }

    public void setLoopCardinalityExpression(Expression loopCardinalityExpression) {
        this.loopCardinalityExpression = loopCardinalityExpression;
    }

    public Expression getCompletionConditionExpression() {
        return completionConditionExpression;
    }

    public void setCompletionConditionExpression(Expression completionConditionExpression) {
        this.completionConditionExpression = completionConditionExpression;
    }

    public Expression getCollectionExpression() {
        return collectionExpression;
    }

    public void setCollectionExpression(Expression collectionExpression) {
        this.collectionExpression = collectionExpression;
    }

    public String getCollectionVariable() {
        return collectionVariable;
    }

    public void setCollectionVariable(String collectionVariable) {
        this.collectionVariable = collectionVariable;
    }

    public String getCollectionElementVariable() {
        return collectionElementVariable;
    }

    public void setCollectionElementVariable(String collectionElementVariable) {
        this.collectionElementVariable = collectionElementVariable;
    }

    public String getCollectionElementIndexVariable() {
        return collectionElementIndexVariable;
    }

    public void setCollectionElementIndexVariable(String collectionElementIndexVariable) {
        this.collectionElementIndexVariable = collectionElementIndexVariable;
    }

    public void setInnerActivityBehavior(AbstractBpmnActivityBehavior innerActivityBehavior) {
        this.innerActivityBehavior = innerActivityBehavior;
        this.innerActivityBehavior.setMultiInstanceActivityBehavior(this);
    }

    public AbstractBpmnActivityBehavior getInnerActivityBehavior() {
        return innerActivityBehavior;
    }

    public String getLoopDataOutputRef() {
        return loopDataOutputRef;
    }

    public boolean hasLoopDataOutputRef() {
        return loopDataOutputRef != null && !loopDataOutputRef.trim().isEmpty();
    }

    public void setLoopDataOutputRef(String loopDataOutputRef) {
        this.loopDataOutputRef = loopDataOutputRef;
    }

    public String getOutputDataItem() {
        return outputDataItem;
    }

    public boolean hasOutputDataItem() {
        return outputDataItem != null && !outputDataItem.trim().isEmpty();
    }

    public void setOutputDataItem(String outputDataItem) {
        this.outputDataItem = outputDataItem;
    }

    protected void updateResultCollection(DelegateExecution childExecution, DelegateExecution miRootExecution) {
        if (miRootExecution != null && hasLoopDataOutputRef()) {
            Object loopDataOutputReference = miRootExecution.getVariableLocal(getLoopDataOutputRef());
            List<Object> resultCollection;
            if (loopDataOutputReference instanceof List) {
                resultCollection = (List<Object>) loopDataOutputReference;
            } else {
                resultCollection = new ArrayList<>();
            }
            resultCollection.add(getResultElementItem(childExecution));
            setLoopVariable(miRootExecution, getLoopDataOutputRef(), resultCollection);
        }
    }

    protected Object getResultElementItem(DelegateExecution childExecution) {
        CommandContext commandContext = getCommandContext();
        if (commandContext != null && commandContext.getCommand() instanceof CompleteTaskCmd) {
            //in the case of a User Task, the variables are directly attached to the TaskEntity
            //and not in the child execution. CompleteTaskCmd will delete the task and all its
            //variables, but before doing so it's keeping a cache of existing local variables that
            //can be retrieve here and used int the result collection.
            Map<String, Object> taskVariables = ((CompleteTaskCmd) commandContext.getCommand()).getTaskVariables();
            return getResultElementItem(taskVariables);
        }
        // in the case where it's not a User Task, the local variables will be available directly
        // in the child execution
        return getResultElementItem(childExecution.getVariablesLocal());
    }

    protected CommandContext getCommandContext() {
        return Context.getCommandContext();
    }

    protected Object getResultElementItem(Map<String, Object> availableVariables) {
        if (hasOutputDataItem()) {
            return availableVariables.get(getOutputDataItem());
        } else {
            //exclude from the result all the built-in multi-instances variables
            //and loopDataOutputRef itself that may exist in the context depending
            // on the variable propagation
            List<String> resultItemExclusions = Arrays.asList(
                    getLoopDataOutputRef(),
                    getCollectionElementIndexVariable(),
                    NUMBER_OF_INSTANCES,
                    NUMBER_OF_COMPLETED_INSTANCES,
                    NUMBER_OF_ACTIVE_INSTANCES);
            HashMap<String, Object> resultItem = new HashMap<>(availableVariables);
            resultItem.keySet().removeAll(resultItemExclusions);
            return resultItem;
        }
    }

    protected void propagateLoopDataOutputRefToProcessInstance(ExecutionEntity miRootExecution) {
        if (hasLoopDataOutputRef()) {
            miRootExecution.getProcessInstance().setVariable(getLoopDataOutputRef(), miRootExecution.getVariable(getLoopDataOutputRef()));
        }
    }

    public List<Map<String, String>> getNodeInfo(DelegateExecution execution, BpmnModel bpmnModel) {
        List<Map<String, String>> result = new ArrayList<>();
        FlowElement flowElement = execution.getCurrentFlowElement();
        KaiteBaseUserTask kaiteBaseUserTask = (KaiteBaseUserTask) flowElement;
        //获取节点的进线
        List<SequenceFlow> list = kaiteBaseUserTask.getIncomingFlows();
        //获取历史最近的一个
        List<HistoricActivityInstance> historicActivityInstanceList = Context.getCommandContext()
                .getProcessEngineConfiguration().getHistoryService().createHistoricActivityInstanceQuery().processInstanceId(execution.getProcessInstanceId())
                .orderByHistoricActivityInstanceStartTime().desc().list();
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
            for (SequenceFlow sequenceFlow : list) {
                if (sequenceFlow.getSourceRef().equals(historicActivityInstance.getActivityId())) {
                    FlowElement userTask = bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getSourceRef());
                    if (userTask != null) {
                        result.add(getNodeInfo(execution.getProcessInstanceId(), userTask));
                        break;
                    }
                }
            }
        }
        return result;
    }

    private Map<String, String> getNodeInfo(String piid, FlowElement userTask) {
        CommandContext commandContext = Context.getCommandContext();
        Map<String, String> nodeInfo = new HashMap<>();
        HistoricTaskInstanceQueryImpl historicTaskInstanceQuery = new HistoricTaskInstanceQueryImpl();
        historicTaskInstanceQuery.processInstanceId(piid);
        historicTaskInstanceQuery.taskDefinitionKey(userTask.getId());
        historicTaskInstanceQuery.orderByTaskCreateTime().desc();
        List<HistoricTaskInstance> links = commandContext.getProcessEngineConfiguration().getHistoricTaskInstanceEntityManager()
                .findHistoricTaskInstancesByQueryCriteria(historicTaskInstanceQuery);
        if (links.size() == 0) {
            return nodeInfo;
        }
        nodeInfo.put("directTaskId", userTask.getId());
        nodeInfo.put("directTaskName", userTask.getName());
        String prevAssignee = "";
        KaiteBaseUserTask kaiteBaseUserTask = (KaiteBaseUserTask) userTask;
        //是否是多实例任务节点，如果是从历史变量中获取处理人信息，如果不是取上个节点的处理人信息
        if (kaiteBaseUserTask.hasMultiInstanceLoopCharacteristics()) {
            Page page = new Page(0, 10);
            HistoricVariableInstanceQueryImpl historicVariableInstanceQuery = new HistoricVariableInstanceQueryImpl();
            historicVariableInstanceQuery.processInstanceId(piid);
            historicVariableInstanceQuery.variableName("datas");
            historicVariableInstanceQuery.executionId(links.get(0).getExecutionId());
            List<HistoricVariableInstance> historicVariableInstanceList = commandContext.getProcessEngineConfiguration()
                    .getHistoricVariableInstanceEntityManager().
                            findHistoricVariableInstancesByQueryCriteria(historicVariableInstanceQuery, page);
            if (historicVariableInstanceList.size() > 0) {
                Object value = historicVariableInstanceList.get(0).getValue();
                if (value instanceof ArrayList) {
                    List<String> userIds = (List<String>) value;
                    prevAssignee = userIds.stream().collect(Collectors.joining(","));
                }
            }
        } else {
            prevAssignee = links.get(0).getAssignee();
        }
        nodeInfo.put("prevAssignee", prevAssignee);
        return nodeInfo;
    }

    /**
     * 添加审批告知变量
     *
     * @param
     */
    public void setVar(DelegateExecution execution, SubmitTypeEnum submitTypeEnum, String comment) {
        JSONObject jsonObject = new JSONObject();
        if (execution.getVariable(MultiInstanceActivityBehavior.PROCESSING_USERS_INFO) != null) {
            List<String> value = (List) execution.getVariable(MultiInstanceActivityBehavior.PROCESSING_USERS_INFO);
            jsonObject.put("assignee", String.join(",", value));
        } else {
            jsonObject.put("assignee", Authentication.getAuthenticatedUser().getDeptId());
        }
        jsonObject.put("comment", comment);
        jsonObject.put("submitType", submitTypeEnum.getName());
        jsonObject.put("taskId", "");
        //是否多人
        FlowElement currentFlowElement = execution.getCurrentFlowElement();
        jsonObject.put("isMulti", false);
        //审批告知类型
        List<TaskApprovalNoticeEnum> taskApprovalNoticeEnumList = new ArrayList<>();
        if (currentFlowElement instanceof KaiteBaseUserTask) {
            //获取节点上审批告知的配置信息
            TaskApprovalNoticeConfigImpl taskApprovalNoticeConfig = ((KaiteBaseUserTask) currentFlowElement).getTaskApprovalNoticeConfig();
            //判断是否是流程发起人收到通知
            if (taskApprovalNoticeConfig.isStartUser()) {
                taskApprovalNoticeEnumList.add(TaskApprovalNoticeEnum.STARTUSER);
            }
            //判断是否是已审批人员收到通知
            if (taskApprovalNoticeConfig.isApprovedPerson()) {
                taskApprovalNoticeEnumList.add(TaskApprovalNoticeEnum.APPROVEDPERSON);
            }
            //判断是否是直属领导收到通知
            if (taskApprovalNoticeConfig.isApproverDirectLeader()) {
                taskApprovalNoticeEnumList.add(TaskApprovalNoticeEnum.APPROVERDIRECTLEADER);
            }
            //判断是否是部门领导收到通知
            if (taskApprovalNoticeConfig.isApproverDeptLeader()) {
                taskApprovalNoticeEnumList.add(TaskApprovalNoticeEnum.APPROVERDEPTLEADER);
            }
        }
        if (taskApprovalNoticeEnumList.size() == 0) {
            return;
        }
        jsonObject.put("approvalNoticeType", taskApprovalNoticeEnumList);
        Context.getCommandContext().addAttribute(CommandContext.APPROVALNOTICE, jsonObject);
    }

    /**
     * 添加审批告知
     */
    public void addApprovalNotice(CommandContext commandContext, DelegateExecution execution, String submitType, String comment, String assigneeUser) {
        Object variable = commandContext.getAttribute(CommandContext.APPROVALNOTICE);
        if (variable instanceof JSONObject) {
            JSONObject jsonObject = (JSONObject) variable;
            //提交人
            String assignee = jsonObject.getString("assignee");
            //审批意见
            if (Strings.isNullOrEmpty(comment)) {
                comment = jsonObject.getString("comment");
            }
            //提交类型name
            if (Strings.isNullOrEmpty(submitType)) {
                submitType = jsonObject.getString("submitType");
            }
            //审批告之事件配置类型
            List<TaskApprovalNoticeEnum> taskApprovalNoticeEnumList =
                    (List<TaskApprovalNoticeEnum>) jsonObject.get("approvalNoticeType");
            if (null == taskApprovalNoticeEnumList || taskApprovalNoticeEnumList.size() == 0) {
                return;
            }
            //多人节点从变量datas获取代办人，单人节点从task获取
            String toAssignee = assigneeUser;

            String beanId = execution.getProcessInstanceBusinessKey();
            String pdId = execution.getProcessDefinitionId();
            //获取bpmnModel
            BpmnModel bpmnModel = commandContext.getProcessEngineConfiguration().getRepositoryService().getBpmnModel(pdId);
            String modelName = bpmnModel.getMainProcess().getName();
            //获取审批告知的Bean信息
            ActivitiApprovalNotice approvalNotice = commandContext.getProcessEngineConfiguration().getActivitiApprovalNotice();
            //send
            approvalNotice.sendNotice(taskApprovalNoticeEnumList, execution, assignee, comment, submitType, toAssignee, modelName, beanId);
        }
    }

    public void setDirectTaskIdVar(DelegateExecution execution) {
        execution.setTransientVariable(KaiteBaseUserTaskActivityBehavior.DIRECT_TASK_ID, execution.getCurrentActivityId());
        execution.setTransientVariable(KaiteBaseUserTaskActivityBehavior.DIRECT_TASK_NAME, execution.getCurrentFlowElement().getName());
        execution.setTransientVariable(KaiteBaseUserTaskActivityBehavior.DIRECT_TASK_TARGET, ((Activity) execution.getCurrentFlowElement()).getOutgoingFlows());
    }

    protected void buildCounterInfo(DelegateExecution execution, DelegateExecution miRootExecution) {
        CommandContext commandContext = getCommandContext();
        nrOfPassInstances = getLoopVariable(execution, NUMBER_OF_PASS_INSTANCES);
        nrOfVetoInstances = getLoopVariable(execution, NUMBER_OF_VETO_INSTANCES);
        nrOfAbstainInstances = getLoopVariable(execution, NUMBER_OF_ABSTAIN_INSTANCES);
        principalOpinion = getStringVariable(execution, PRINCIPAL_OPINION);
        oneBallotUserId = getStringVariable(execution, PASS_PRINCIPAL);
        if (commandContext.getAttribute(COUNTERSIGN_PASS_USER) != null && commandContext.getAttribute(CUSTOMER_MESSAGE) != null) {
            addProcessingInfo(miRootExecution, commandContext.getAttribute(COUNTERSIGN_PASS_USER).toString(), commandContext.getAttribute(CUSTOMER_MESSAGE).toString());
            if (commandContext.getAttribute(CUSTOMER_MESSAGE).toString().equals(ActivitiCountersignedOpinionType.PASS.toString())) {
                nrOfPassInstances += 1;
                if (commandContext.getAttribute(COUNTERSIGN_PASS_USER).toString().equals(oneBallotUserId)) {
                    principalOpinion = ActivitiCountersignedOpinionType.PASS.toString();
                }
            } else if (commandContext.getAttribute(CUSTOMER_MESSAGE).toString().equals(ActivitiCountersignedOpinionType.VETO.toString())) {
                nrOfVetoInstances += 1;
                if (commandContext.getAttribute(COUNTERSIGN_PASS_USER).toString().equals(oneBallotUserId)) {
                    principalOpinion = ActivitiCountersignedOpinionType.VETO.toString();
                }
            } else if (commandContext.getAttribute(CUSTOMER_MESSAGE).toString().equals(ActivitiCountersignedOpinionType.ABSTAIN.toString())) {
                nrOfAbstainInstances += 1;
            }
        }
    }

    protected void setCounterInfo(DelegateExecution miRootExecution) {
        setLoopVariable(miRootExecution, NUMBER_OF_PASS_INSTANCES, nrOfPassInstances);
        setLoopVariable(miRootExecution, NUMBER_OF_VETO_INSTANCES, nrOfVetoInstances);
        setLoopVariable(miRootExecution, NUMBER_OF_ABSTAIN_INSTANCES, nrOfAbstainInstances);
        setLoopVariable(miRootExecution, PRINCIPAL_OPINION, principalOpinion);
    }

    protected void leaveRemoveCounterVar(DelegateExecution execution) {
        execution.removeVariable(NUMBER_OF_PASS_INSTANCES);
        execution.removeVariable(NUMBER_OF_VETO_INSTANCES);
        execution.removeVariable(NUMBER_OF_ABSTAIN_INSTANCES);
        execution.removeVariable(PRINCIPAL_OPINION);
        execution.removeVariable("directTaskId");
        execution.removeVariable("form");
        execution.removeVariable(PROCESSING_USERS_INFO);

    }

}
